package ui;

import java.awt.Graphics;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.FontMetrics;

/**
 * A class for simple x-y plots, showing up to 3 traces in a cartesian
 * coordinate system.
 * 
 * @version 1.0
 * @author Hans U. Gerber (<a
 *         href="mailto:gerber@ifh.ee.ethz.ch">gerber@ifh.ee.ethz.ch</a>)
 */
public class XYPlot extends Plot {
	RealPoint[][] trace;
	Dimension previousSize = null;

	public XYPlot(double minX, double minY, double maxX, double maxY) {
		trace = new RealPoint[3][];
		setWindow(-5.0, -1.1, 5.0, 7.1);
	}

	void setViewport() {
		Dimension d = size();
		super.setViewport(4 + textWidth + 3 + 3, 4, d.width - 4, d.height - 4);
	}

	// private int counter = 0;

	/**
	 * Updates one of 3 traces; the trace is immediately redrawn.
	 */
	public synchronized void setTrace(int which, RealPoint[] p) {
		// I want to update a new trace as quickly and as smoothly as I can.
		// I don't use double-buffering because it does not make sense to
		// move whole bitmaps worth kilobytes when just a few pixels have to
		// be updated.
		// Requesting repaint() does not work with me because
		// - it "calls update() as soon as possible" and several repaint
		// requests may be merged into one
		// - by calling update(), the background would be erased
		// getGraphics() should work fine for this task but then I ran into
		// a JDK AWT bug. It showed up with appletviewer and HotJava:
		// I should be able to getGraphics() as many times as I want.
		// But it seems to me that Windows resources (DCs?) are not properly
		// freed. After a lot of calls, getGraphics() returns null.
		// MS' jview and Explorer 3.0 ran fine, however.
		// When I replaced MS' implementation of Win32Graphics.class in Sun's
		// classes.zip, the problem went away. As a precaution, I now
		// explicitely
		// dispose() the graphics context.
		// But then, this happened:
		// When testing the applet with Sun's JDK java interpreter and
		// appletviewer under Windows 95, the plot did not appear initially.
		// I noticed that setTrace() caused paint() to be called when the
		// size of the canvas was still [0,0]. Maybe the AWT then assumed that
		// the canvas was already painted and did not repaint again.
		// Now I have setTrace() call paint() only if the size is not zero:
		//
		Graphics g = getGraphics();
		Dimension d = size();
		boolean canPaint = (g != null && d.width != 0 && d.height != 0);
		if (canPaint) {
			// Erase the current image of the trace by painting it with
			// the background color. This may cut holes into other traces
			// and the axis. Therefore, I will repaint the whole plot after
			// this:
			setViewport();
			drawTrace(g, trace[which], getBackground());
		}
		trace[which] = p;
		if (canPaint) {
			// Repaint all to fix any holes:
			paint(g);
		}
		if (g != null) {
			// As a quick fix for Sun's Windows resource leak bug,
			// I explicitely free the graphics context:
			g.dispose();
			// (Before knew about dispose(), I used to do this:
			// Collect garbage from time to time, hoping that
			// Windows resources are freed as well:
			//
			// counter++;
			// if (counter > 100) {
			// System.gc();
			// counter = 0;
			// }
		}
	}

	static final int FRAME_WIDTH = 4;
	int textWidth = 0;
	int textAscent = 0;

	/**
	 * This method repaints the plot completely.
	 */
	public synchronized void paint(Graphics g) {
		// After resizing a component, the AWT doesn't seem to call neither
		// repaint() nor update(). It goes directly at paint().
		// But I want a cleanly erased canvas after a resize, because not all
		// of my drawing takes place in repaint().
		// I force an update() (and therefore erasing the canvas)
		// whenever paint() notices that the size of the canvas has changed:
		//
		Dimension d = size();
		if (previousSize == null || previousSize.width != d.width
				|| previousSize.height != d.height) {
			previousSize = d;
			update(g);
			// This will erase the background and then call paint() recursively.
			// Recursion stops as soon as changing the size stops.
		}

		if (textWidth == 0) {
			FontMetrics metrics = g.getFontMetrics();
			textWidth = metrics.stringWidth("+1");
			textAscent = metrics.getAscent();
		}

		// Draw a 3-D frame:
		Color bg = getBackground();
		g.setColor(bg);
		g.draw3DRect(0, 0, d.width - 1, d.height - 1, true);
		g.draw3DRect(3, 3, d.width - 7, d.height - 7, false);
		g.setColor(Color.black);
		g.clipRect(4, 4, d.width - 8, d.height - 8);
		setViewport();
		g.setColor(Color.gray);
		g.drawLine(xformX(xMin), xformY(0), xformX(xMax), xformY(0));
		g.drawLine(xformX(0.0), xformY(yMax), xformX(0.0), xformY(yMin));
		g.drawString("0", xformX(0.0) - textWidth, xformY(0.0) - 1);
		g.drawLine(xformX(1.0), xformY(0) - 3, xformX(1.0), xformY(0.0) + 3);
		g.drawString(" 1", xformX(1.0) - textWidth / 2, xformY(0.0) + 3
				+ textAscent);
		g.drawLine(xformX(0.0) - 3, xformY(1.0), xformX(0.0) + 3, xformY(1.0));
		g.drawString("+1", xformX(0.0) - 3 - textWidth - 1, xformY(1.0)
				+ textAscent / 2);
		g.drawLine(xformX(0.0) - 3, xformY(-1.0), xformX(0.0) + 3, xformY(-1.0));
		g.drawString("-1", xformX(0.0) - 3 - textWidth - 1, xformY(-1.0)
				+ textAscent / 2);
		drawTrace(g, trace[0], new Color(255, 0, 0));
		drawTrace(g, trace[1], new Color(0, 0, 0));
		drawTrace(g, trace[2], new Color(0, 0, 255));
	}

	/**
	 * Draws one of the 3 traces.
	 */
	synchronized void drawTrace(Graphics g, RealPoint[] trace, Color color) {
		if (trace != null) {
			int nrOfPoints = trace.length;
			int[] x = new int[nrOfPoints];
			int[] y = new int[nrOfPoints];
			for (int i = 0; i < nrOfPoints; i++) {
				x[i] = xformX(trace[i].x);
				y[i] = xformY(trace[i].y);
			}
			g.setColor(color);
			// With older Java VMs, drawPolygon did not close the polygon.
			// The newest VMs, however, do close it; and the documentation says
			// so.
			// This is dumb. A programmer can easily close a polyline, if he
			// wants to.
			// But there's no easy way to keep drawPolygon from closing the
			// line.
			// Do I really have to resort to a series of drawLines?
			//
			// g.drawPolygon(x, y, nrOfPoints);
			for (int i = 0; i < nrOfPoints - 1; i++) {
				g.drawLine(x[i], y[i], x[i + 1], y[i + 1]);
			}
		}
	}
}
